package com.linkai.JUC._15_single;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/**
 * @Description: 懒汉式单例模式
 *               （1）加锁防止多线程破坏单例   DCL 懒汉式
 *               （2）加上 volatile 关键字避免指令重排保证单例
 *               （3）通过反射破坏单例
 *               （4）构造器加锁，红绿灯设置隐藏变量防止反射破坏
 *               （5）反射修改该隐藏变量依旧可以破坏单例
 *               ----> 道高一尺，魔高一尺！ 使用枚举解决
 * @Author: 林凯
 * @Date: 2021/10/26 11:39
 */
public class _02_LazyMan {

    // 红绿灯，设置一个隐藏变量；防止反射破解单例（但是依旧可以解决，使用反射修改这个变量即可）
    // 不通过反编译的情况下，是找不到这个变量的
    private static boolean jacklin = false;

    private _02_LazyMan() {
        // 在这里增加代码，防止用反射来破坏单例
        synchronized (_02_LazyMan.class) {
            if (jacklin == false) {
                jacklin = true;
            } else {
                throw new RuntimeException("不要试图使用反射来破坏单例！");

            }
//            if (lazyMan != null) {
//                throw new RuntimeException("不要试图使用反射来破坏单例！");
//            }
        }

        System.out.println(Thread.currentThread().getName() + " ok ");
    }

    /*------------------------------------------------------------------------*/
    // 不加 volatile 还依旧不安全，由于指令重排可能导致出错
//    private static LazyMan lazyMan;

    // 未加锁的方法，多线程下不能保证单例
    public static _02_LazyMan getInstance() {
        if (lazyMan == null) {
            lazyMan = new _02_LazyMan();
        }
        return lazyMan;
    }

    /*------------------------------------------------------------------------*/

    // 正确的 DCL 懒汉式需要加上 volatile 关键避免指令重排
    private volatile static _02_LazyMan lazyMan;

    // 双重检测锁模式的懒汉式单例：简称 DCL 懒汉式
    public static _02_LazyMan getInstanceWithLock() {
        if (lazyMan == null) {
            synchronized (_02_LazyMan.class) {
                if (lazyMan == null) {
                    /*
                        不是原子性操作
                            1. 分配内存空间
                            2. 执行构造方法，初始化对象
                            3. 把这个对象指向这个空间
                        可能存在指令重排：
                            理论：123
                            实际可能：132
                        A线程执行：132
                        执行完3之后B线程调度，误以为 lazyMan != null 直接返回了
                    * */
                    lazyMan = new _02_LazyMan();
                }
            }
        }
        return lazyMan;
    }

    /*------------------------------------------------------------------------*/



    // 模拟多线程下破坏单例（如果未加锁的就可能创建多个实例）
    public static void testThread() {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                // 未加锁，可能创建多个实例
//                LazyMan.getInstance();
                // 加锁了，可以保证单例
                _02_LazyMan.getInstanceWithLock();
            }).start();
        }
    }

    // 测试通过反射来破坏单例（在构造方法里面不做处理的话，可以破坏单例模式）
    public static void testReflect() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        // 正常情况下这两个对象是一样的
//        LazyMan instance1 = LazyMan.getInstance();
//        LazyMan instance2 = LazyMan.getInstance();
        // 使用反射来破坏单例模式

        Field jacklin = _02_LazyMan.class.getDeclaredField("jacklin");
        jacklin.setAccessible(true);

        // 1. 获取无参构造器
        Constructor<_02_LazyMan> declaredConstructor = _02_LazyMan.class.getDeclaredConstructor(null);
        // 2. 设置 accessible
        declaredConstructor.setAccessible(true);
        // 3. 通过这个构造器来 new 对象
        // 如果2个对象都是通过反射获取的，则不能捕获异常（2个对象都是通过 newInstace() 获得的 ）
        _02_LazyMan instance3 = declaredConstructor.newInstance();
        // 将该隐藏变量的值修改，破坏反射
        jacklin.set(instance3, false);
        _02_LazyMan instance4 = declaredConstructor.newInstance();

        System.out.println(instance3);
        System.out.println(instance4);
    }

    // 单线程下可以保持单例，但是多线程下可能会有多个实例
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException {

        testReflect();
    }

}
